<template>
  <div>
    <div v-show="nowPage === 'home'">
      <button @click="startCamera" :disabled="!hasReady">开启摄像头</button>
      <button @click="copyData">复制</button>
      <button @click="clearData">清空数据</button>
      <div class="code-in-db">
        <p>{{`当前已录入的发票号码（${codesInDB.length}个）：`}}</p>
        <p v-for="item in codesInDB" :key="item">
          <span>{{ item }}</span>
          <button @click="delData(item)">删</button>
        </p>
      </div>
    </div>
    <div v-show="nowPage === 'ocr'">
      <div class="showArea">
        <canvas id="canvas" v-show="canvasVisable"></canvas>
        <video id="video" />
      </div>
      <p v-show="msgOnOcr">{{ msgOnOcr }}</p>
      <p v-show="!msgOnOcr">{{ `当前已录入 ${codesInDB.length} 个发票。(平均耗时：二维码识别 ${qrcodeAvgTime} 秒、数字识别 ${hTesseractAvgTime} 秒）)` }}</p>
      <div class="btns">
        <button @click="stopCamera" :disabled="!hasReady">关闭摄像头</button>
        <button @click="changeCamera" :disabled="!hasReady">切换摄像头</button>
      </div>
      <span>按下数字键可从以下数字选择一个录入，如果都不对，可<a @click="clearItemOfOcr">按回车键清空</a>后再扫描</span>
      <ul class="codes-for-select">
        <li v-for="(item, index) in matchCodes" :key="item">
          <span :class="{
            first: matchCodesTime[item] === matchCodesTimeSort[0],
            second: matchCodesTime[item] === matchCodesTimeSort[1],
          }">{{ `${item} (${matchCodesTime[item] || 1} 次识别到)` }}</span>
          <button @click="addDataFromOcr(index)">选择</button>
        </li>
      </ul>
    </div>
    <div v-show="nowPage === 'new'">
      <p>{{`新增：${newCode}`}}</p>
      <p>{{`已录入 ${codesInDB.length} 个`}}</p>
      <p>{{ newCodeCount }}</p>
    </div>
  </div>
</template>

<script setup>
import {onMounted, onUnmounted, ref, watch} from 'vue'
import DeviceCamera from './utils/camera'
import HTesseract from './utils/tesseract'

const nowPage = ref('home')
const hasReady = ref(true)
const cameraFront = ref(false)
const msgOnOcr = ref('')
const matchCodes = ref([])
const matchCodesTime = ref({})
const matchCodesTimeSort = ref([])
const codesInDB = ref([])
const newCode = ref('')
const newCodeCount = ref(0)
const canvasDom = ref()
const canvasCtx = ref()
const canvasVisable = ref(false)
const hTesseractAvgTime = ref(0)
const qrcodeAvgTime = ref(0)

// 现在是用摄像头原始的尺寸，不用这里的设置裁剪了，有待考察性能
const width = 200
const height = 200

watch(cameraFront, async () => {
  try{
    stopCamera()
    await startCamera()
  } catch (err) {
    console.error(err);
  }
})

watch(codesInDB, (value) => {
  localStorage.setItem("invoice-codes", JSON.stringify(value))
})

const deviceCamera = new DeviceCamera(width, height)
const hTesseract = new HTesseract()

onMounted(() => {
  canvasDom.value = document.getElementById('canvas')
  canvasCtx.value = canvasDom.value.getContext('2d')
  document.addEventListener('keypress', keyboardEvent, false)
  const dbData = localStorage.getItem("invoice-codes");
  if (dbData) {
    codesInDB.value = JSON.parse(dbData)
  }
})
onUnmounted(()=>{
  document.removeEventListener('keypress', keyboardEvent)
  stopCamera()
})

function keyboardEvent(e){
    const keycode = e.code
    let hadHandle = true
    console.log(keycode);
    switch (keycode) {
      case 'Enter':
        startCamera()
        clearItemOfOcr()
        break
      case 'Digit1':
        addDataFromOcr(0, true)
        break
      case 'Digit2':
        addDataFromOcr(1, true)
        break
      case 'Digit3':
        addDataFromOcr(2, true)
        break
      case 'Digit4':
        addDataFromOcr(3, true)
        break
      case 'Digit5':
        addDataFromOcr(4, true)
        break
      case 'Digit6':
        addDataFromOcr(5, true)
        break
      case 'Digit7':
        addDataFromOcr(6, true)
        break
      case 'Digit8':
        addDataFromOcr(7, true)
        break
      case 'Digit9':
        addDataFromOcr(8, true)
        break
      default:
        hadHandle = false
    }
    if (!hadHandle) {
      e.preventDefault();
    }
}

function addDataFromOcr(index, ignoreIfNoValue){
  if (!matchCodes.value[index]) {
    if (!ignoreIfNoValue) {
      alert('数据有问题，请再识别一次')
      clearItemOfOcr()
    }
    return
  }
  addData(matchCodes.value[index])
}

function addData(d){
  if (codesInDB.value.includes(d)){
    showMsgOnOcr(`此发票号码已存在:${d}`)
    return
  }
  canvasVisable.value = false
  showNewCode(d)
  codesInDB.value = [...codesInDB.value, d]
  clearItemOfOcr()
}
function delData(d){
  const index = codesInDB.value.indexOf(d)
  if (index < 0) {
    return
  }
  codesInDB.value.splice(index, 1)
  codesInDB.value = [...codesInDB.value]
}

function clearData(){
  const res = confirm('确定清空已经录入的全部发票号码吗？')
  if (!res){
    return
  }
  codesInDB.value = []
}
function copyData(){
  const text = codesInDB.value.join('\n')
  navigator.clipboard.writeText(text)
  alert('复制成功')
}

function clearItemOfOcr(){
  matchCodes.value = []
  matchCodesTime.value = {}
} matchCodesTimeSort.value = []

async function startCamera(){
  if (deviceCamera.active) {
    return
  }
  hasReady.value = false
  nowPage.value = 'ocr'
  const video = startOcr()
  await deviceCamera.startCamera(video, cameraFront.value)
  hasReady.value = true
}
function stopCamera(){
  nowPage.value = 'home'
  deviceCamera.stopCamera()
  hTesseract.stopOcrFromVideo()
}
function startOcr() { 
  const video = document.getElementById('video')
  hTesseract.startOcrFromVideo(video, (text, source, canvas) => {
    if (source === 'qrcode') {
      qrcodeAvgTime.value = (hTesseract.qrAvg / 1000).toFixed(2)
      // 01,31, ,23312000000070357096,4986.00,20230904, ,405F
      const temp = text.split(',')
      const code = temp[3]
      console.log(text);
      if (!code) {
        return
      }
      addData(code)
    } else {
      hTesseractAvgTime.value = (hTesseract.avgTime / 1000).toFixed(2)
      // 把码加到待选区
      if (!text || text.length < 8 || nowPage.value !== 'ocr') {
        return
      }
      const codes = Array.from(text.matchAll(/[^\d]{0,3}(\d{8}|\d{20})[^\d]/g))
      if (codes.length) {
        const times = {...matchCodesTime.value }
        const temp = []
        codes.forEach(d => {
          temp.push(d[1])
          times[d[1]] = (times[d[1]] || 0) + 1
        })
        matchCodesTime.value = times
        const sort = Object.values(matchCodesTime.value)
        if (sort.length >= 3) {
          sort.sort((a, b) => b - a)
          if (sort[0] !== sort[1]) {
            matchCodesTimeSort.value = [sort[0]]
            if (sort[1] !== sort[2]) {
              matchCodesTimeSort.value.push(sort[1])
            }
          }
        }
        const newCodes = [...new Set([...matchCodes.value, ...temp])]
        if (newCodes.length > matchCodes.value.length) {
          matchCodes.value = newCodes
          canvasDom.value.width = canvas.width
          canvasDom.value.height = canvas.height
          canvasCtx.value.drawImage(canvas, 0, 0, canvas.width, canvas.height)
          canvasVisable.value = true
        }
      }
    }
  })
  return video
}
function changeCamera(){
  cameraFront.value = !cameraFront.value
}

function showNewCode(code, msg){
  const oldPage = nowPage.value
  hTesseract.stopOcrFromVideo()
  newCode.value = code
  nowPage.value = 'new'
  newCodeCount.value = 5
  const inter = setInterval(() => {
    newCodeCount.value = newCodeCount.value - 1
    if (newCodeCount.value <= 0) {
      clearInterval(inter)
      newCode.value =''
      startOcr()
      nowPage.value = oldPage
    }
  }, 1000)
}

let msgOnOcrInter = 0
function showMsgOnOcr(msg){
  if (msgOnOcrInter) {
    clearInterval(msgOnOcrInter)
    msgOnOcrInter = 0
  }
  msgOnOcr.value = msg || ''
  if (!msg) {
    return
  }
  let count = 5
  msgOnOcrInter = setInterval(() => {
    msgOnOcr.value = `${msg}(${count})`
    count -= 1
    if (count <= 0) {
      clearInterval(msgOnOcrInter)
      msgOnOcrInter = 0
      msgOnOcr.value = ''
    }
  }, 1000)
}
</script>

<style scoped>
a{
  cursor: pointer;
}
.codes-for-select{
    list-style-type: decimal;
    text-align: start;
}
.codes-for-select li span {
  padding: 0 0.5em;
}
.codes-for-select li .first{
  background: #65d41a;
}
.codes-for-select li .second{
  background: #bcee9b;
}
.code-in-db {
    text-align: left;
}
.showArea > * {
display: inline-block;
  width: 200px;
}
</style>
